﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Orchard.Tests;
using NUnit.Framework;
using Orchard.Core.Title.Models;
using Downplay.Mechanics.Models;
using Orchard.ContentManagement;

namespace Downplay.Science.Tests.Mechanics {
    public class ServiceTests : MechanicsTestsBase {

        private string[] categoryNames = new string[]{ "Fruit","Veg","Sweets" };
        private string[] fruitNames = new string[] { "Apple", "Orange", "Pear" };

        [Test]
        public void CreateAConnector() {

            InitializeContentTypes();

            var left = _contentManager.New(DefaultCategoryName);
            left.As<TitlePart>().Title = categoryNames.First();
            _contentManager.Create(left);
            _contentManager.Clear();

            var right = _contentManager.New(DefaultProductName);
            _contentManager.Create(right);
            ClearSession();
            _contentManager.Clear();

            _mechanics.CreateConnector(left, right, DefaultCategoryToProductName, true);
            ClearSession();
            _contentManager.Clear();
            
            // Now poll
            var connectors = _mechanics.Connectors(left, DefaultCategoryToProductName);

            Assert.That(connectors.Count(), Is.EqualTo(1), "Single connector created");
            var connector = connectors.List().First();
            Assert.That(connector.ContentItem.ContentType, Is.EqualTo(DefaultCategoryToProductName), "Created correct connector type");
            Assert.That(connector.RightContent, Is.Not.Null, "Has right-hand item");
            Assert.That(connector.RightContent.ContentItem.ContentType, Is.EqualTo(DefaultProductName)); // Connects to correct category
            Assert.That(connector.RightContent.ContentItem.Id, Is.EqualTo(right.Id));

            // Check it created correct sequence number
            Assert.That(connector.As<SequencePart>().Sequence, Is.EqualTo(1));

            // Poll for inverse
            var inverseConnectors = _mechanics.Connectors(right, DefaultProductToCategoryName);
            Assert.That(inverseConnectors.Count(), Is.EqualTo(1), "Inverse connector created");

            var inverse = inverseConnectors.List().First();
            Assert.That(inverse.ContentItem.ContentType, Is.EqualTo(DefaultProductToCategoryName), "Created correct inverse type");
            Assert.That(inverse.RightContent, Is.Not.Null, "Inverse has right-hand item");
            Assert.That(inverse.RightContent.ContentItem.ContentType, Is.EqualTo(DefaultCategoryName), "Inverse has correct right content type"); // Connects to correct category
            Assert.That(inverse.RightContent.ContentItem.Id, Is.EqualTo(left.Id), "Inverse has correct right content id");

            // Check title has been set
            Assert.That(inverse.As<TitlePart>().Title, Is.EqualTo(categoryNames.First()));

        }

        [Test]
        public void ConnectorsWithVersionedSockets() {

            InitializeContentTypes(true);

            var left = _contentManager.New(DefaultCategoryName);
            left.As<TitlePart>().Title = categoryNames.First();
            _contentManager.Create(left);

            var right = _contentManager.New(DefaultProductName);
            _contentManager.Create(right);
            ClearSession();
            _contentManager.Clear();

            _mechanics.CreateConnector(left, right, DefaultCategoryToProductName, true);
            ClearSession();
            _contentManager.Clear();

            var connectors = _mechanics.Connectors(left, DefaultCategoryToProductName);

            Assert.That(connectors.Count(), Is.EqualTo(1), "Single connector created");
            var connector = connectors.List().First();
            Assert.That(connector.ContentItem.ContentType, Is.EqualTo(DefaultCategoryToProductName), "Created correct connector type");
            Assert.That(connector.RightContent, Is.Not.Null, "Has right-hand item");
            Assert.That(connector.RightContent.ContentItem.ContentType, Is.EqualTo(DefaultProductName)); // Connects to correct category
            Assert.That(connector.RightContent.ContentItem.Id, Is.EqualTo(right.Id));

            var inverseConnectors = _mechanics.Connectors(right, DefaultProductToCategoryName);
            Assert.That(inverseConnectors.Count(), Is.EqualTo(1), "Inverse connector created");

            var inverse = inverseConnectors.List().First();
            Assert.That(inverse.ContentItem.ContentType, Is.EqualTo(DefaultProductToCategoryName), "Created correct inverse type");
            Assert.That(inverse.RightContent, Is.Not.Null, "Inverse has right-hand item");
            Assert.That(inverse.RightContent.ContentItem.ContentType, Is.EqualTo(DefaultCategoryName), "Inverse has correct right content type"); // Connects to correct category
            Assert.That(inverse.RightContent.ContentItem.Id, Is.EqualTo(left.Id), "Inverse has correct right content id");

            // Now update the content
            left = _contentManager.Get(left.Id);
            right = _contentManager.Get(right.Id);

            _contentManager.Publish(left);
            _contentManager.Publish(right);
            ClearSession();
            _contentManager.Clear();

            connectors = _mechanics.Connectors(left, DefaultCategoryToProductName);

            Assert.That(connectors.Count(), Is.EqualTo(1), "Single connector created");
            connector = connectors.List().First();
            Assert.That(connector.ContentItem.ContentType, Is.EqualTo(DefaultCategoryToProductName), "Created correct connector type");
            Assert.That(connector.RightContent, Is.Not.Null, "Has right-hand item");
            Assert.That(connector.RightContent.ContentItem.ContentType, Is.EqualTo(DefaultProductName)); // Connects to correct category
            Assert.That(connector.RightContent.ContentItem.Id, Is.EqualTo(right.Id));

            inverseConnectors = _mechanics.Connectors(right, DefaultProductToCategoryName);
            Assert.That(inverseConnectors.Count(), Is.EqualTo(1), "Inverse connector created");

            inverse = inverseConnectors.List().First();
            Assert.That(inverse.ContentItem.ContentType, Is.EqualTo(DefaultProductToCategoryName), "Created correct inverse type");
            Assert.That(inverse.RightContent, Is.Not.Null, "Inverse has right-hand item");
            Assert.That(inverse.RightContent.ContentItem.ContentType, Is.EqualTo(DefaultCategoryName), "Inverse has correct right content type"); // Connects to correct category
            Assert.That(inverse.RightContent.ContentItem.Id, Is.EqualTo(left.Id), "Inverse has correct right content id");

            ClearSession();
            _contentManager.Clear();
            // Check from sockets

            left = _contentManager.Get(left.Id, VersionOptions.Published);
            right = _contentManager.Get(right.Id,VersionOptions.Published);

            var leftConnectors = left.As<SocketsPart>().Sockets.Socket(DefaultCategoryToProductName);

            Assert.That(leftConnectors.ConnectorItems.Count(), Is.EqualTo(1));
            var rightAcrossSocket = leftConnectors.RightContent.FirstOrDefault();
            Assert.That(rightAcrossSocket, Is.Not.Null);
            Assert.That(rightAcrossSocket.ContentItem.Id, Is.EqualTo(right.Id));

            // Invertse

            var rightConnectors = right.As<SocketsPart>().Sockets.Socket(DefaultProductToCategoryName);

            Assert.That(rightConnectors.ConnectorItems.Count(), Is.EqualTo(1));
            var leftAcrossSocket = rightConnectors.RightContent.FirstOrDefault();
            Assert.That(leftAcrossSocket, Is.Not.Null);
            Assert.That(leftAcrossSocket.ContentItem.Id, Is.EqualTo(left.Id));

        }

        [Test]
        public void VersionedConnectorsWithVersionedSockets() {

            InitializeContentTypes(true,true);

            var left = _contentManager.New(DefaultCategoryName);
            left.As<TitlePart>().Title = categoryNames.First();
            _contentManager.Create(left);

            var right = _contentManager.New(DefaultProductName);
            _contentManager.Create(right);
            _contentManager.Flush();
            ClearSession();
            _contentManager.Clear();

            _mechanics.CreateConnector(left, right, DefaultCategoryToProductName, true);
            _contentManager.Flush();
            ClearSession();
            _contentManager.Clear();

            var connectors = _mechanics.Connectors(left, DefaultCategoryToProductName);

            Assert.That(connectors.Count(), Is.EqualTo(1), "Single connector created");
            var connector = connectors.List().First();
            Assert.That(connector.ContentItem.ContentType, Is.EqualTo(DefaultCategoryToProductName), "Created correct connector type");
            Assert.That(connector.RightContent, Is.Not.Null, "Has right-hand item");
            Assert.That(connector.RightContent.ContentItem.ContentType, Is.EqualTo(DefaultProductName)); // Connects to correct category
            Assert.That(connector.RightContent.ContentItem.Id, Is.EqualTo(right.Id));

            var inverseConnectors = _mechanics.Connectors(right, DefaultProductToCategoryName);
            Assert.That(inverseConnectors.Count(), Is.EqualTo(1), "Inverse connector created");

            var inverse = inverseConnectors.List().First();
            Assert.That(inverse.ContentItem.ContentType, Is.EqualTo(DefaultProductToCategoryName), "Created correct inverse type");
            Assert.That(inverse.RightContent, Is.Not.Null, "Inverse has right-hand item");
            Assert.That(inverse.RightContent.ContentItem.ContentType, Is.EqualTo(DefaultCategoryName), "Inverse has correct right content type"); // Connects to correct category
            Assert.That(inverse.RightContent.ContentItem.Id, Is.EqualTo(left.Id), "Inverse has correct right content id");

            // Now update the content
            left = _contentManager.Get(left.Id);
            right = _contentManager.Get(right.Id);

            _contentManager.Publish(left);
            _contentManager.Publish(right);
            _contentManager.Flush();
            ClearSession();
            _contentManager.Clear();

            connectors = _mechanics.Connectors(left, DefaultCategoryToProductName);

            Assert.That(connectors.Count(), Is.EqualTo(1), "Single connector created");
            connector = connectors.List().First();
            Assert.That(connector.ContentItem.ContentType, Is.EqualTo(DefaultCategoryToProductName), "Created correct connector type");
            Assert.That(connector.RightContent, Is.Not.Null, "Has right-hand item");
            Assert.That(connector.RightContent.ContentItem.ContentType, Is.EqualTo(DefaultProductName)); // Connects to correct category
            Assert.That(connector.RightContent.ContentItem.Id, Is.EqualTo(right.Id));

            inverseConnectors = _mechanics.Connectors(right, DefaultProductToCategoryName);
            Assert.That(inverseConnectors.Count(), Is.EqualTo(1), "Inverse connector created");

            inverse = inverseConnectors.List().First();
            Assert.That(inverse.ContentItem.ContentType, Is.EqualTo(DefaultProductToCategoryName), "Created correct inverse type");
            Assert.That(inverse.RightContent, Is.Not.Null, "Inverse has right-hand item");
            Assert.That(inverse.RightContent.ContentItem.ContentType, Is.EqualTo(DefaultCategoryName), "Inverse has correct right content type"); // Connects to correct category
            Assert.That(inverse.RightContent.ContentItem.Id, Is.EqualTo(left.Id), "Inverse has correct right content id");

            // Now create a draft

            var leftDraft = _contentManager.Get(left.Id, VersionOptions.DraftRequired);
            leftDraft.As<TitlePart>().Title = categoryNames.ElementAt(1);
            _contentManager.Flush();

            var socketQuery = leftDraft.As<SocketsPart>().Sockets.Socket(DefaultCategoryToProductName);

            // Enumerate the query, otherwise the items won't get created
            var list = socketQuery.Connectors.List();
            Assert.That(list.Count(), Is.EqualTo(1));
            Assert.That(list.First().ContentItem.IsPublished(), Is.EqualTo(false));

            _contentManager.Flush();
            socketQuery = leftDraft.As<SocketsPart>().Sockets.Socket(DefaultCategoryToProductName);
            var inspectConnectors = socketQuery.Connectors.List();
            var inspectQuery = socketQuery.ConnectorQuery.List();
            Assert.That(socketQuery.TotalCount, Is.EqualTo(1));
            // Fortunately right content hasn't gone into editor mode ... at least, not yet.
            Assert.That(socketQuery.RightContent.Count(), Is.EqualTo(1));
            Assert.That(socketQuery.RightContent.First(), Is.Not.Null);

            ClearSession();
            _contentManager.Clear();

            leftDraft = _contentManager.Get(left.Id, VersionOptions.Draft);
            Assert.That(leftDraft, Is.Not.Null);
            Assert.That(leftDraft.As<TitlePart>().Title,Is.EqualTo(categoryNames.ElementAt(1)));

            socketQuery = leftDraft.As<SocketsPart>().Sockets.Socket(DefaultCategoryToProductName);

            Assert.That(socketQuery.TotalCount, Is.EqualTo(1));
            Assert.That(socketQuery.RightContent.Count(), Is.EqualTo(1));
            Assert.That(socketQuery.RightContent.First(), Is.Not.Null);

            // We're not done yet
            ClearSession();
            _contentManager.Clear();

            // Test the inverse still works (should point to original item)
            right = _contentManager.Get(right.Id);
            var rightConnectors = right.As<SocketsPart>().Sockets.Socket(DefaultProductToCategoryName);
            Assert.That(rightConnectors.Connectors.Count(), Is.EqualTo(1));
            Assert.That(rightConnectors.RightContent.Count(), Is.EqualTo(1));
            // Check it's still pointing to the published version
            Assert.That(rightConnectors.RightContent.First().IsPublished(), Is.EqualTo(true));
            Assert.That(rightConnectors.RightContent.First().ContentItem.VersionRecord.Id, Is.EqualTo(left.VersionRecord.Id));

            // Publish again
            ClearSession();
            _contentManager.Clear();

            leftDraft = _contentManager.Get(left.Id, VersionOptions.Draft);
            _contentManager.Publish(leftDraft);
            _contentManager.Flush();
            ClearSession();
            _contentManager.Clear();

            leftDraft = _contentManager.Get(left.Id, VersionOptions.Latest);
            Assert.That(leftDraft, Is.Not.Null);
            Assert.That(leftDraft.IsPublished(), Is.EqualTo(true));
            Assert.That(leftDraft.As<TitlePart>().Title, Is.EqualTo(categoryNames.ElementAt(1)));

            socketQuery = leftDraft.As<SocketsPart>().Sockets.Socket(DefaultCategoryToProductName);

            Assert.That(socketQuery.TotalCount, Is.EqualTo(1));
            Assert.That(socketQuery.Connectors.List().First().IsPublished(), Is.EqualTo(true));
            Assert.That(socketQuery.RightContent.Count(), Is.EqualTo(1));
            Assert.That(socketQuery.RightContent.First(), Is.Not.Null);
            Assert.That(socketQuery.RightContent.First().ContentItem.Id == right.Id, Is.Not.Null);

            ClearSession();
            _contentManager.Clear();

            // Test the inverse still works (should point to new item)
            right = _contentManager.Get(right.Id);
            rightConnectors = right.As<SocketsPart>().Sockets.Socket(DefaultProductToCategoryName);
            Assert.That(rightConnectors.ConnectorItems.Count(), Is.EqualTo(1));
            Assert.That(rightConnectors.RightContent.Count(), Is.EqualTo(1));
            // Check it's still pointing to the published version
            Assert.That(rightConnectors.RightContent.First().ContentItem.VersionRecord.Id, Is.EqualTo(leftDraft.VersionRecord.Id));
            Assert.That(rightConnectors.RightContent.First().IsPublished(), Is.EqualTo(true));

            // Going to do it once more but without all the flushing
            // Now create a draft
            leftDraft = _contentManager.Get(left.Id, VersionOptions.DraftRequired);
            leftDraft.As<TitlePart>().Title = categoryNames.ElementAt(1);
            _contentManager.Flush();
            _contentManager.Publish(leftDraft);
            _contentManager.Flush();
            var versionId = leftDraft.VersionRecord.Id;
            ClearSession();
            _contentManager.Clear();

            leftDraft = _contentManager.Get(left.Id, VersionOptions.Latest);
            Assert.That(leftDraft, Is.Not.Null);
            Assert.That(leftDraft.IsPublished(), Is.EqualTo(true));
            Assert.That(leftDraft.VersionRecord.Id, Is.EqualTo(versionId));
            Assert.That(leftDraft.As<TitlePart>().Title, Is.EqualTo(categoryNames.ElementAt(1)));

            socketQuery = leftDraft.As<SocketsPart>().Sockets[DefaultCategoryToProductName];

            Assert.That(socketQuery.TotalCount, Is.EqualTo(1));
            var first = socketQuery.Connectors.List().First();
            Assert.That(first.IsPublished(), Is.EqualTo(true));
            Assert.That(first.LeftContentVersionId, Is.EqualTo(leftDraft.VersionRecord.Id));
            Assert.That(socketQuery.RightContent.Count(), Is.EqualTo(1));
            Assert.That(socketQuery.RightContent.First(), Is.Not.Null);
            Assert.That(socketQuery.RightContent.First().ContentItem.Id == right.Id, Is.Not.Null);

            // Test the inverse still works (should point to new item)
            right = _contentManager.Get(right.Id);
            rightConnectors = right.As<SocketsPart>().Sockets.Socket(DefaultProductToCategoryName);
            Assert.That(rightConnectors.ConnectorItems.First().RightContentVersionId, Is.EqualTo(leftDraft.VersionRecord.Id));
            Assert.That(rightConnectors.ConnectorItems.Count(), Is.EqualTo(1));
            Assert.That(rightConnectors.RightContent.Count(), Is.EqualTo(1));
            // Check it's still pointing to the published version
            Assert.That(rightConnectors.RightContent.First().ContentItem.VersionRecord.Id, Is.EqualTo(leftDraft.VersionRecord.Id));
            Assert.That(rightConnectors.RightContent.First().IsPublished(), Is.EqualTo(true));
        }

        [Test]
        public void VersionedConnectorsWithVersionedSocketsPublishImmediately() {
            // Comparable to Content AdminController CreateAndPublishPOST

            InitializeContentTypes(true, true);
            // RHS is already published
            var right = _contentManager.New(DefaultCategoryName);
            right.As<TitlePart>().Title = categoryNames.First();
            _contentManager.Create(right);
            _contentManager.Publish(right);

            Assert.That(right.IsPublished(), Is.EqualTo(true));
            Assert.That(right.Id, Is.Not.EqualTo(0));

            ClearSession();
            _contentManager.Clear();

            right = _contentManager.Get(right.Id);
            Assert.That(right.IsPublished(), Is.EqualTo(true));
            
            // Create product
            var left = _contentManager.New(DefaultProductName);
            left.As<TitlePart>().Title = "Apple";
            left.As<SocketsPart>().Sockets.Socket(DefaultProductToCategoryName).Connectors.Add(right.Id);
            _contentManager.Create(left); 
            _contentManager.Publish(left);

            ClearSession();
            _contentManager.Clear();

            right = _contentManager.Get(right.Id);
            Assert.That(right.IsPublished(), Is.EqualTo(true));
            var connectorsFromRight = right.As<SocketsPart>().Sockets.Socket(DefaultCategoryToProductName);
            Assert.That(connectorsFromRight.TotalCount, Is.EqualTo(1));
            Assert.That(connectorsFromRight.ConnectorItems.Count(), Is.EqualTo(1));
            Assert.That(connectorsFromRight.ConnectorItems.First().IsPublished(), Is.EqualTo(true));
            Assert.That(connectorsFromRight.RightContent.Count(), Is.EqualTo(1));
            
            left = _contentManager.Get(left.Id);
            Assert.That(left.IsPublished(), Is.EqualTo(true));
            var connectorsFromLeft = left.As<SocketsPart>().Sockets.Socket(DefaultProductToCategoryName);
            Assert.That(connectorsFromLeft.TotalCount, Is.EqualTo(1));
            Assert.That(connectorsFromLeft.ConnectorItems.Count(), Is.EqualTo(1));
            Assert.That(connectorsFromLeft.ConnectorItems.First().IsPublished(), Is.EqualTo(true)); 
            Assert.That(connectorsFromLeft.RightContent.Count(), Is.EqualTo(1));
            
        }

        [Test]
        public void SocketQueryRetrievesConnectors() {
            InitializeContentTypes(true);

            var left = _contentManager.New(DefaultCategoryName);
            left.As<TitlePart>().Title = categoryNames.First();
            _contentManager.Create(left);

            var right = _contentManager.New(DefaultProductName);
            _contentManager.Create(right);
            ClearSession();
            _contentManager.Clear();

            _mechanics.CreateConnector(left, right, DefaultCategoryToProductName, true);
            ClearSession();
            _contentManager.Clear();

            left = _contentManager.Get(left.Id);
            var connectors = left.As<SocketsPart>().Sockets.Socket(DefaultCategoryToProductName);

            Assert.That(connectors.TotalCount, Is.EqualTo(1), "Counted connectors through socket query");
            Assert.That(connectors.RightContent.Count(), Is.EqualTo(1), "Counted right content through socket query");
            Assert.That(connectors.RightContent.FirstOrDefault(), Is.Not.Null, "Accessed right item");
        }

        [Test]
        public void QuickCreateAndPublish() {
            InitializeContentTypes(true, true);
            var left = _contentManager.New(DefaultCategoryName);
            left.As<TitlePart>().Title = categoryNames.First();

            var right1 = _contentManager.New(DefaultProductName);
            right1.As<TitlePart>().Title = fruitNames.ElementAt(0);
            var right2 = _contentManager.New(DefaultProductName);
            right2.As<TitlePart>().Title = fruitNames.ElementAt(1);
            var right3 = _contentManager.New(DefaultProductName);
            right3.As<TitlePart>().Title = fruitNames.ElementAt(2);

            left.As<SocketsPart>().Sockets[DefaultCategoryToProductName].Connectors.Add(new[]{right1,right2,right3});

            _contentManager.Create(left);
            _contentManager.Publish(left); // Should publish everything
            _contentManager.Flush();
            ClearSession();
            _contentManager.Clear();

            var leftPub = _contentManager.Query<TitlePart, TitlePartRecord>(DefaultCategoryName).ForVersion(VersionOptions.Published).List().SingleOrDefault();
            Assert.That(leftPub, Is.Not.Null);
            Assert.That(leftPub.As<SocketsPart>().Sockets[DefaultCategoryToProductName].TotalCount, Is.EqualTo(3));
            Assert.That(leftPub.As<SocketsPart>().Sockets[DefaultCategoryToProductName].RightContent.Select(prod => prod.As<TitlePart>().Title).SequenceEqual(fruitNames), Is.EqualTo(true)); 

        }
    }
}

